﻿/* *****************************************************************************
 * @ Encoding: UTF-8
 * @ File Name: naughty_differential.cpp
 * @ Author: cal
 * @ Email: naughtygeng@qq.com
 * @ Created Time: 2022-Jul-06(Wednesday) 11:01:05
 * @ All Rights Reserved
 * *****************************************************************************/

#include "naughty_differential.hpp"
#include "naughty_differential_node_add.hpp"
#include "naughty_differential_node_base.hpp"
#include "naughty_differential_node_mul.hpp"
#include <cassert>
#include <memory>
#include <vector>
#include <algorithm>
#include <cstdio>

namespace naughty_differential
{

void factorization(std::shared_ptr<node_base> &node_ptr)
{
    if (node_ptr->get_node_type() == node_type_mul)
    {
        // 展开参数中含有加法的乘法
        if (node_ptr->get_parameters()[0]->get_node_type() == node_type_add)
        {
            node_ptr = std::make_shared<node_add>(std::make_shared<node_mul>(node_ptr->get_parameters()[0]->get_parameters()[0], node_ptr->get_parameters()[1]), std::make_shared<node_mul>(node_ptr->get_parameters()[0]->get_parameters()[1], node_ptr->get_parameters()[1]));
        }
        else if (node_ptr->get_parameters()[1]->get_node_type() == node_type_add)
        {
            node_ptr = std::make_shared<node_add>(std::make_shared<node_mul>(node_ptr->get_parameters()[1]->get_parameters()[0], node_ptr->get_parameters()[0]), std::make_shared<node_mul>(node_ptr->get_parameters()[1]->get_parameters()[1], node_ptr->get_parameters()[0]));
        }
        // 展开参数中含有减法的乘法
        else if (node_ptr->get_parameters()[0]->get_node_type() == node_type_sub)
        {
            node_ptr = std::make_shared<node_sub>(std::make_shared<node_mul>(node_ptr->get_parameters()[0]->get_parameters()[0], node_ptr->get_parameters()[1]), std::make_shared<node_mul>(node_ptr->get_parameters()[0]->get_parameters()[1], node_ptr->get_parameters()[1]));
        }
        else if (node_ptr->get_parameters()[1]->get_node_type() == node_type_sub)
        {
            node_ptr = std::make_shared<node_sub>(std::make_shared<node_mul>(node_ptr->get_parameters()[1]->get_parameters()[0], node_ptr->get_parameters()[0]), std::make_shared<node_mul>(node_ptr->get_parameters()[1]->get_parameters()[1], node_ptr->get_parameters()[0]));
        }
    }

    for (std::shared_ptr<node_base> &param : node_ptr->get_parameters())
    {
        factorization(param);
    }

func_end:
    return;
}

void node_sort(std::shared_ptr<node_base> &node_ptr)
{
	for (std::shared_ptr<node_base> &param : node_ptr->get_parameters())
	{
		node_sort(param);
	}
	if (node_ptr->get_node_type() == node_type_add)
	{
		std::shared_ptr<node_add> node_add_ptr = std::dynamic_pointer_cast<node_add>(node_ptr);
		assert(node_add_ptr);
		std::vector<std::shared_ptr<node_base>> leave_nodes = node_add_ptr->get_continual_add_parameters();
		std::sort(leave_nodes.begin(), leave_nodes.end(), [](const std::shared_ptr<node_base> &a, const std::shared_ptr<node_base> &b)->bool{ return a->identity_string() < b->identity_string(); });

		node_ptr = nullptr;
		for (std::shared_ptr<node_base> &node : leave_nodes)
		{
			if (node_ptr)
			{
                node_ptr = std::make_shared<node_add>(node_ptr, node);
            }
			else 
			{
				node_ptr = node;
			}
		}

	}
	if (node_ptr->get_node_type() == node_type_mul)
	{
		std::shared_ptr<node_mul> node_mul_ptr = std::dynamic_pointer_cast<node_mul>(node_ptr);
		assert(node_mul_ptr);
		std::vector<std::shared_ptr<node_base>> leave_nodes = node_mul_ptr->get_continual_mul_parameters();
		std::sort(leave_nodes.begin(), leave_nodes.end(), [](const std::shared_ptr<node_base> &a, const std::shared_ptr<node_base> &b)->bool{ return a->identity_string() < b->identity_string(); });

		node_ptr = nullptr;
		for (std::shared_ptr<node_base> &node : leave_nodes)
		{
			if (node_ptr)
			{
                node_ptr = std::make_shared<node_mul>(node_ptr, node);
            }
			else 
			{
				node_ptr = node;
			}
		}
	}

	return;
}

void merge_similar_items(std::shared_ptr<node_base> &node_ptr)
{

}

void val_mul_to_pow(std::shared_ptr<node_base> &node_ptr)
{
    return;
}

void mul_node_optimize(std::shared_ptr<node_base> &node_ptr) {

  assert(node_ptr->get_node_type() == node_type_mul);
  if (node_ptr->get_parameters()[0]->get_node_type() == node_type_num) {
    if (node_ptr->get_parameters()[0]->get_value({}) == 0) {
      node_ptr = std::make_shared<node_num>(0);
	  goto func_end;
    }
    if (node_ptr->get_parameters()[0]->get_value({}) == 1) {
      node_ptr = node_ptr->get_parameters()[1];
      goto func_end;
    }
  }
  if (node_ptr->get_parameters()[1]->get_node_type() == node_type_num) {
    if (node_ptr->get_parameters()[1]->get_value({}) == 0) {
      node_ptr = std::make_shared<node_num>(0);
	  goto func_end;
    }
    if (node_ptr->get_parameters()[1]->get_value({}) == 1) {
      node_ptr = node_ptr->get_parameters()[0];
	  goto func_end;
    }
  }

func_end:
  return;
}

void div_node_optimize(std::shared_ptr<node_base> &node_ptr)
{
	assert(node_ptr->get_node_type() == node_type_div);
	if (node_ptr->get_parameters()[0]->get_node_type() == node_type_num)
	{
		if (node_ptr->get_parameters()[0]->get_value({}) == 0)
		{
			node_ptr = std::make_shared<node_num>(0);
			goto func_end;
		}
	}
	if (node_ptr->get_parameters()[1]->get_node_type() == node_type_num)
	{
		if (node_ptr->get_parameters()[1]->get_value({}) == 1)
		{
			node_ptr = node_ptr->get_parameters()[0];
			goto func_end;
		}
	}
func_end:
	return;
}

void add_node_optimize(std::shared_ptr<node_base> &node_ptr)
{
	assert(node_ptr->get_node_type() == node_type_add || node_ptr->get_node_type() == node_type_sub);
	if (node_ptr->get_parameters()[0]->get_node_type() == node_type_num)
	{
		if (node_ptr->get_parameters()[0]->get_value({}) == 0)
		{
			node_ptr = node_ptr->get_parameters()[1];
			goto func_end;
		}
	}
	if (node_ptr->get_parameters()[1]->get_node_type() == node_type_num)
	{
		if (node_ptr->get_parameters()[1]->get_value({}) == 0)
		{
			node_ptr = node_ptr->get_parameters()[0];
			goto func_end;
		}
	}
func_end:
	return;
}

void node_optimize(std::shared_ptr<node_base> &node_ptr)
{
	for (std::shared_ptr<node_base> &node_param_ptr : node_ptr->get_parameters())
	{
		node_optimize(node_param_ptr);
	}
	if (node_ptr->get_node_type() == node_type_mul)
	{
        mul_node_optimize(node_ptr);
		goto func_end;
    }
	if (node_ptr->get_node_type() == node_type_add || node_ptr->get_node_type() == node_type_sub)
	{
		add_node_optimize(node_ptr);
		goto func_end;
	}
	if (node_ptr->get_node_type() == node_type_div)
	{
		div_node_optimize(node_ptr);
		goto func_end;
	}
func_end:
	return;
}

} // namespace naughty_differential

std::shared_ptr<naughty_differential::node_base> operator+(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_add>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> operator-(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_sub>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> operator*(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_mul>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> operator/(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_div>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> operator^(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_pow>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> log(std::shared_ptr<naughty_differential::node_base> param01, std::shared_ptr<naughty_differential::node_base> param02)
{
    return std::make_shared<naughty_differential::node_log>(param01, param02);
}

std::shared_ptr<naughty_differential::node_base> ln(std::shared_ptr<naughty_differential::node_base> param)
{
    return std::make_shared<naughty_differential::node_ln>(param);
}

std::shared_ptr<naughty_differential::node_base> exp(std::shared_ptr<naughty_differential::node_base> param)
{
    return std::make_shared<naughty_differential::node_exp>(param);
}

std::shared_ptr<naughty_differential::node_base> sin(std::shared_ptr<naughty_differential::node_base> param)
{
    return std::make_shared<naughty_differential::node_sin>(param);
}

std::shared_ptr<naughty_differential::node_base> cos(std::shared_ptr<naughty_differential::node_base> param)
{
    return std::make_shared<naughty_differential::node_cos>(param);
}

std::shared_ptr<naughty_differential::node_base> num(double num)
{
    return std::make_shared<naughty_differential::node_num>(num);
}

std::shared_ptr<naughty_differential::node_base> var(std::string var)
{
    return std::make_shared<naughty_differential::node_var>(var);
}
